/*
 *    Copyright (c) 2018-2025, lengleng All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: lengleng (wangiegie@gmail.com)
 */

package com.liycloud.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.vo.CaptchaVO;
import com.anji.captcha.service.CaptchaService;

import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;


// pigx
import com.liycloud.common.core.constant.CacheConstants;
import com.liycloud.common.core.constant.CommonConstants;
import com.liycloud.common.core.constant.SecurityConstants;
import com.liycloud.common.core.constant.enums.LoginTypeEnum;
import com.liycloud.common.core.exception.ValidateCodeException;
import com.liycloud.common.core.util.ResultVo;
import com.liycloud.common.core.util.SpringContextHolder;
import com.liycloud.common.core.util.WebUtils;
import com.liycloud.gateway.config.FilterIgnorePropertiesConfig;

//
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.data.redis.core.StringRedisTemplate;
// import org.springframework.data.redis.serializer.StringRedisSerializer;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author lengleng
 * @date 2020/5/19 登录逻辑 验证码处理
 * <p>
 * 网关过滤器, 验证码的 处理逻辑
 *
 *   /auth/**  访问认证中心的请求都需要经过两个过滤器:  1. 验证码的处理过滤器， 2.密码解密过滤器
 *
 *   FilterIgnorePropertiesConfig  这里配置的 ignore 客户端,  在 gateway 本验证码过滤器会直接放行,忽略认证
 *
 *
 */
@Slf4j
@Component
@AllArgsConstructor
public class ValidateCodeGatewayFilter extends AbstractGatewayFilterFactory {

	private final ObjectMapper objectMapper;

	private final StringRedisTemplate redisTemplate;

	private final FilterIgnorePropertiesConfig filterIgnorePropertiesConfig;


	@Override
	public GatewayFilter apply(Object config) {

		return new GatewayFilter() {

			@Override
			public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
				ServerHttpRequest request = exchange.getRequest();

				// 请求路径
				String requestPath = request.getURI().getPath();

				// 不是登录请求，直接向下执行
				if (!StrUtil.containsAnyIgnoreCase(
						requestPath,
						SecurityConstants.OAUTH_TOKEN_URL,    //   /oauth/token   		 普通登录(账号密码)
						SecurityConstants.SMS_TOKEN_URL,      //   /mobile/token/sms     手机短信登录
						SecurityConstants.SOCIAL_TOKEN_URL)   //   /mobile/token/social  社交账号登录( 微信 QQ 微博 等)
				) {

					return chain.filter(exchange);  // 放行
				}

				// 刷新token，直接向下执行
				String grantType = request.getQueryParams().getFirst("grant_type");
				if (StrUtil.equals(SecurityConstants.REFRESH_TOKEN, grantType)) {
					return chain.filter(exchange);
				}


				try {

					String header = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);  //请求头中:  Authorization

					// 从请求头中获取客户端信息  Headers ( Authorization:Basic cGlnOnBpZw== )
					String clientId = WebUtils.extractClientId(header).orElse(null);

					// 终端 设置不校验 验证码， 这里如果是在 ignore 中配置 直接向下执行
					if (StrUtil.isNotBlank(clientId) && filterIgnorePropertiesConfig.getClients().contains(clientId)) {
						return chain.filter(exchange);
					}

					// 如果是社交登录，判断是否包含SMS   /mobile/token/social
					if (StrUtil.containsAnyIgnoreCase(requestPath, SecurityConstants.SOCIAL_TOKEN_URL)) {
						String mobile = request.getQueryParams().getFirst("mobile");
						if (StrUtil.containsAny(mobile, LoginTypeEnum.SMS.getType())) {
							throw new ValidateCodeException("验证码不合法");
						} else {
							return chain.filter(exchange);
						}
					}


					// 校验验证码
					// ValidateCodeGatewayFilter.this.checkCode(request);
					checkCode(request);

				} catch (Exception e) {
					ServerHttpResponse response = exchange.getResponse();
					response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
					response.setStatusCode(HttpStatus.PRECONDITION_REQUIRED);
					try {
						return response.writeWith(Mono.just(
								response.bufferFactory().wrap(objectMapper.writeValueAsBytes(ResultVo.failed(e.getMessage())))));
					} catch (JsonProcessingException e1) {
						log.error("对象输出异常", e1);
					}
				}

				// 都没有异常抛出, 则该过滤器通过
				return chain.filter(exchange);
			}
		};
	}


	/**
	 * 检查code
	 *
	 * @param request
	 */
	@SneakyThrows
	private void checkCode(ServerHttpRequest request) {

		// 验证码
		String code = request.getQueryParams().getFirst("code");

		if (StrUtil.isBlank(code)) {
			throw new ValidateCodeException("验证码不能为空");
		}

		// 随机串
		String randomStr = request.getQueryParams().getFirst("randomStr");

		// 还有一种非滑块的 直接发送一个前面图片验证码 (参考Pig的实现)

		// 若是滑块登录  randomStr = blockPuzzle ? 是否满足
		if (CommonConstants.IMAGE_CODE_TYPE.equalsIgnoreCase(randomStr)) {
			// 图形验证码服务实例, 从容器中获取
			CaptchaService captchaService = SpringContextHolder.getBean(CaptchaService.class);
			CaptchaVO vo = new CaptchaVO();
			vo.setCaptchaVerification(code);

			// 滑块验证码 校验不成功就抛出异常
			if (!captchaService.verification(vo).isSuccess()) {
				throw new ValidateCodeException("验证码不能为空");
			}
			return;
		}

		// 手机短信码登录   /mobile/token/sms    eg. DEFAULT_CODE_KEY:SMS@18682098587
		if (StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), SecurityConstants.SMS_TOKEN_URL)) {
			String mobile = request.getQueryParams().getFirst("mobile");
			if (StrUtil.isNotBlank(mobile)) {
				randomStr = mobile;
				log.info("正在进行手机短信SMS登录: {} " ,  randomStr );
			}
		}


		// DEFAULT_CODE_KEY:  验证码前缀
		String key = CacheConstants.DEFAULT_CODE_KEY + randomStr;

		log.info("本次登录验证码Redis的键key为: {} , 验证码的值value为: {} " ,  key, code );

		// redisTemplate.setKeySerializer(new StringRedisSerializer());

		if (!redisTemplate.hasKey(key)) {
			throw new ValidateCodeException("验证码不合法");
		}

		Object codeObj = redisTemplate.opsForValue().get(key);

		if (codeObj == null) {
			throw new ValidateCodeException("验证码不合法");
		}

		String saveCode = codeObj.toString();

		log.info("本次登录验证码 saveCode 为:  {} " , saveCode );

		if (StrUtil.isBlank(saveCode)) {
			//redisTemplate.delete(key);
			throw new ValidateCodeException("验证码不合法");
		}

		if (!StrUtil.equals(saveCode, code)) {
			//redisTemplate.delete(key);
			throw new ValidateCodeException("验证码不合法");
		}

		//redisTemplate.delete(key);
	}

}
